home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / DELPHI32 / SYS_TOOL / MULTI020 / MULTI.DOC < prev    next >
Text File  |  1993-09-07  |  27KB  |  816 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.                                                                   |
  30.                                                                   |
  31.                                                             MULTI |
  32.                                                                   |
  33.                a Borland Pascal unit for cooperative multitasking |
  34.                                                                   |
  35.                                                  version 0.2 beta |
  36.                                                                   |
  37.                           (C) copyright 1993 by Felix von Leitner |
  38.                                                                   |
  39.                              written in 1993 by Felix von Leitner |
  40.                                          leitner@inf.fu-berlin.de |
  41.                                                                   |
  42.                                                                   |
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.     Contents
  60.  
  61.     What Is Multitasking ?
  62.     Is MULTI For Me ?
  63.     Does Not Multitasking Create Many New Problems ?
  64.     How Does Multitasking PASCAL Code Look Like ?
  65.     Advanced Multitasking - Semaphores
  66.     Interprocess Communications Via Pipes
  67.     Things I Have Forgotten So Far
  68.  
  69.  
  70.  
  71.  
  72.  
  73.  
  74.  
  75.  
  76.  
  77.  
  78.  
  79.  
  80.  
  81.  
  82.  
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92.  
  93.  
  94.  
  95.  
  96.  
  97.  
  98.  
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.     Chapter 1: What Is Multitasking ?
  112.  
  113.  
  114.     Multitasking comes from "multi" and  "task", multi being the Latin word
  115.     for  "many" and  "task" meaning  any piece  of work. Multitasking means
  116.     that your  procedures can run  parallelly, and they  can interfere with
  117.     each other, if you want them to.
  118.  
  119.     MULTI is a pure software solution. MULTI can't do miracles, if one task
  120.     crashes,  the whole  program crashes.  MULTI won't  change your program
  121.     into parallel wonders without some work.
  122.  
  123.     In my opinion  multitasking is rather a programming  style than a unit,
  124.     but you  have to have some  tools to do it  efficiently. MULTI provides
  125.     you with these tools.
  126.  
  127.  
  128.  
  129.  
  130.  
  131.  
  132.  
  133.  
  134.  
  135.  
  136.  
  137.  
  138.  
  139.  
  140.  
  141.  
  142.  
  143.  
  144.  
  145.  
  146.  
  147.  
  148.  
  149.  
  150.  
  151.  
  152.  
  153.  
  154.  
  155.  
  156.  
  157.  
  158.  
  159.  
  160.  
  161.  
  162.  
  163.  
  164.  
  165.  
  166.     Chapter 2: Is MULTI For Me ?
  167.  
  168.  
  169.     MULTI is  not for beginners.  I am using  Turbo and Borland  Pascal for
  170.     several years now, I am an experienced Assembler programmer, and I have
  171.     lots of fun  with MULTI. You don't have  to be any of these,  you don't
  172.     have to be much experienced with Borland  Pascal, but you have to be an
  173.     experienced programmer. If you are an experienced C or BASIC progammer,
  174.     you will have little problems with Borland Pascal, and as I said above,
  175.     MULTI is  just the tools for  your multitasking programming. So  I will
  176.     have to explain what multitasking programming means and not only give a
  177.     reference of the function in MULTI.
  178.  
  179.     An integral part in writing programs is going from spaghetti code where
  180.     all data is globally accessible to  local data and encapsulation. In my
  181.     opinion OOP is a great step, it  is not the solution to the problems of
  182.     the world,  but it helps a  lot. You have to  have understood what data
  183.     encapsulation does  for you to be  able to enjoy multitasking.  You can
  184.     have much fun  with MULTI if you write small  programs, but the goal is
  185.     to create big  applications more easily, and for  big applications data
  186.     encapsulation is VERY important.
  187.  
  188.     Data encapsulation means that part A  of your program cannot access the
  189.     data of part B if both parts don't have anything to do with each other.
  190.     Now you not  only have several parts of your  program, you have several
  191.     TASKS, several  procedures running in parallel,  it is ABSOLUTELY FATAL
  192.     if they interfere in each other private data without some insulation. I
  193.     have tried to provide some means  of general insulation with PIPES, but
  194.     like the whole unit, the pipes are not completely finished yet.
  195.  
  196.     I want you  to understand that  multitasking can radically  restructure
  197.     your  world of  programming, it  can be  a great  help (and  a boost in
  198.     programming time) if you really think your concepts to the end.
  199.  
  200.     One example of  trivial multitasking is a jump and  run game, where all
  201.     the sprites have their own task  which directs them. Another example is
  202.     a printer spooler or  a serial communications spooler. Or  a disk cache
  203.     which works in  the background ! Everything is possible.  Or you have a
  204.     task which reorganizes your data if nothing else is done. Your database
  205.     creates a report while the user does something else !
  206.  
  207.  
  208.  
  209.  
  210.  
  211.  
  212.  
  213.  
  214.  
  215.  
  216.  
  217.  
  218.  
  219.  
  220.  
  221.     Chapter 3: Does Not Multitasking Create Many New Problems ?
  222.  
  223.  
  224.     If you think  of "real" multitasking there would  be many new problems.
  225.     No task could be sure no other  task has changed their variables in the
  226.     midst  of  a  calculation  !  But  MULTI  uses  a  different  approach,
  227.     cooperative multitasking. That means, each  task does the switching, so
  228.     you can switch when the task is ready to be interrupted.
  229.  
  230.  
  231.  
  232.  
  233.  
  234.  
  235.  
  236.  
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  
  247.  
  248.  
  249.  
  250.  
  251.  
  252.  
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260.  
  261.  
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.  
  271.  
  272.  
  273.  
  274.  
  275.  
  276.     Chapter 4: How Does Multitasking PASCAL Code Look Like ?
  277.  
  278.     Well,  I don't  want to  show  you  real multitasking  code, I  want to
  279.     demonstrate how MULTI grew according to  my needs. My first idea was to
  280.     just have a procedure which runs while the main program runs, too.
  281.  
  282.     │  uses crt, multi;  { This example does NOT work, see below ! }
  283.     │
  284.     │  procedure task; far;
  285.     │  begin
  286.     │    repeat
  287.     │      writeln('Task running !');
  288.     │    until false;
  289.     │  end;
  290.     │
  291.     │  begin
  292.     │    Fork(task);  { Activate the task running in the background }
  293.     │  end.
  294.  
  295.     Look nice, but is not sufficient. Several problem are there. MULTI does
  296.     not do  miracles, it won't switch  tasks automatically, you have  to do
  297.     that. The procedure for that is called Switch.
  298.  
  299.     │  uses crt, multi;  { This does not work, too. See below ! }
  300.     │
  301.     │  procedure task;
  302.     │  begin
  303.     │    repeat
  304.     │      writeln('Task running !');
  305.     │      Switch;  { Switch away }
  306.     │    until false
  307.     │  end;
  308.     │
  309.     │  begin
  310.     │    Fork(task);
  311.     │    repeat
  312.     │      Switch
  313.     │    until keypressed;
  314.     │  end.
  315.  
  316.     This  is closer  to the  real MULTI,  but it  ain't enough neither. You
  317.     could write  program using this method,  but the next problem  is : Can
  318.     you use  local variables in the  task procedure ? Yes  you can. Can you
  319.     use global variables  from within it ? Yes you  can. You can call other
  320.     procedures, you  can even call  recursive procedures (procedures  which
  321.     call themselves), if you understand  the mechanism of procedure calling
  322.     and local procedures, you know that these use up stack. But MULTI can't
  323.     do miracles, it does not know how much stack it should allocate for the
  324.     task, the task  cannot use the main stack (the  only one Borland Pascal
  325.     knows of), because the main task uses  it, and no two tasks may disturb
  326.     each other's stack.  So MULTI has to allocate memory  from the heap and
  327.     give it to each task procedure as stack. The procedure won't know which
  328.     stack is  uses, and it  does not matter.  So you have  to tell FORK how
  329.     much stack to give the task. The next version of the program looks like
  330.     that :
  331.  
  332.     │  uses crt, multi;  { Still does not work, but gets closer }
  333.     │
  334.     │  procedure task; far;
  335.     │  var i : word
  336.     │  begin
  337.     │    for i := 1 to 1000 do begin
  338.     │      writeln('In task !');
  339.     │      Switch
  340.     │    end
  341.     │  end;
  342.     │
  343.     │  begin
  344.     │    Fork(task,2048);
  345.     │    repeat
  346.     │      Switch
  347.     │    until keypressed
  348.     │  end.
  349.  
  350.     All right, even closer to a real program. 2048 is a fine stack space if
  351.     you don't call other procedures  which call other procedures which call
  352.     other ... you get the idea. 2048 is enough for the procedure above. You
  353.     can see that the task now has  no endless loop in it. Mhh, what happens
  354.     when it exits ? Maybe your system crashes ? No problem with MULTI ! The
  355.     task simply terminates. That means,  calls to SWITCH won't activate the
  356.     task again, it's stack space is given back to the heap again, the local
  357.     variables are gone. The above program prints  up to 1000 'In task !' if
  358.     you don't press a key before that, then it exits.
  359.  
  360.     In the above  examples I assumed that if the  main program exits (maybe
  361.     through a run-time error) no task is called again. If a task has opened
  362.     a file and  just wanted to save  some important data, the  data are not
  363.     saved. That is not desireable. But more  about that later. Now I want a
  364.     task that prints 'Task  A !' and one task which prints  'Task B !'. How
  365.     could I do that ? You have to  give the task a parameter. But you don't
  366.     call the task, FORK  does ! Mhh, so you have to  tell Fork what to give
  367.     the  task as  parameters. Since  I  don't  want a  Fork for  procedures
  368.     without parameters, and one for  every possible number of parameters, I
  369.     write a  Fork which takes one  argument (an untyped VAR  parameter). So
  370.     the program looks like this :
  371.  
  372.  
  373.  
  374.  
  375.  
  376.  
  377.  
  378.  
  379.  
  380.  
  381.  
  382.  
  383.  
  384.  
  385.  
  386.     │  uses crt, multi;  { That's it ! }
  387.     │
  388.     │  procedure task(var m); far;
  389.     │  var
  390.     │    s : string absolute m;
  391.     │    i : word;
  392.     │  begin
  393.     │    for i := 1 to 1000 do begin
  394.     │      writeln(s);
  395.     │      switch
  396.     │    end
  397.     │  end;
  398.     │
  399.     │  var
  400.     │    s : string;
  401.     │
  402.     │  begin
  403.     │    s := 'Task A !';
  404.     │    Fork(task,2048,s);
  405.     │    s := 'Task B !';
  406.     │    Fork(task,2048,s);
  407.     │    repeat
  408.     │      switch
  409.     │    until keypressed
  410.     │  end.
  411.  
  412.     What do  you think this  code does ?  It prints 'Task  A !', then  1999
  413.     times 'Task  B !' ! You  see one problem with  the parameters, they are
  414.     pointer. Both tasks get the address of the same variable, so if we want
  415.     one task printing 'Task A !' and one printing 'Task B !', we could have
  416.     the task procedures save the string to  a local variable or we can have
  417.     the  main program  allocate space  for the  task name  on the  heap and
  418.     copying the wanted string there, or we  could have s and s1 as strings,
  419.     one containing 'Task A !' and one 'Task B !'. That's a drawback, right.
  420.     But what can I  do ? What if I want to pass  TWO parameters to a task ?
  421.     You have to define a record and pass that. Sorry for the inconvenience,
  422.     but the result rewards you.
  423.  
  424.     Now to the other problems. You have  seen that you can terminate a task
  425.     with exit. This  is a hack for convenience, and  you should use it, but
  426.     historically there is another way to do that : call Terminate. It frees
  427.     the stack  and SWITCHes to  the next task.  If no tasks  are there, the
  428.     program  ends. If  you call  Terminate  from  the main  program, it  is
  429.     treated as task and terminated, the other tasks still run. If you wrote
  430.     a task which has an endless loop  in it and terminate the main program,
  431.     your program runs forever !
  432.  
  433.     What happens  if the main task  ends by reaching the  'end.' and not by
  434.     Terminate ? You should think that  all tasks are terminated then, since
  435.     noone calls  SWITCH again. And  that's the way  it was. And  it's still
  436.     so in a way. These tasks are nice, but if you want to do something from
  437.     the real world with them, you fail. Because they can't react to errors,
  438.     if  anybody produces  a run-time  error, no  task is  called again. Now
  439.     imagine a task doing serial communications. That task has to setup some
  440.     interrupts and it  HAS to de-initialize them or  the system will crash.
  441.     Normally one would use the EXITPROC mechanism for that, but EXITPROC is
  442.     global, and we want to encapsulate  the tasks, we want to insulate them
  443.     from each  other (the main task  being just one of  the tasks). So each
  444.     procedure should have something EXITPROC  like. The simples way I could
  445.     imagine is by  letting SWITCH return a boolean  value. FALSE means that
  446.     everything is  still fine, TRUE means  that we have an  error condition
  447.     and must deinit and terminate NOW.  But not every task needs that extra
  448.     treatment.  Some tasks  can be  killed directly,  and we  don't want to
  449.     complicate matters by  having those tasks react to  SWITCH's result. If
  450.     SWITCH wants  to switch to  such a task  under an error  condition that
  451.     task is  simply killed. That sounds  incredibly difficult, this example
  452.     illustrates everything :
  453.  
  454.     │  uses crt, multi;
  455.     │
  456.     │  procedure standard_task(var m); far;
  457.     │  begin
  458.     │    repeat
  459.     │      writeln('This task does not deinitialize');
  460.     │      Switch;
  461.     │    until false;
  462.     │    writeln('This is never written')
  463.     │  end;
  464.     │
  465.     │  procedure new_task(var m); far;
  466.     │  begin
  467.     │    t^.hasexit := true;
  468.     │      { Tell MULTI we want to deinitialize ! }
  469.     │    repeat
  470.     │      writeln('This task DOES deinitialize');
  471.     │      if Switch then break;  { break goes behind the "until false" }
  472.     │    until false;
  473.     │    writeln('This is the deinitialization');
  474.     │  end;
  475.     │
  476.     │  begin
  477.     │    Fork(standard_task,2048,Nothing);
  478.     │    Fork(new_task,2048,Nothing);
  479.     │  end.
  480.  
  481.     'Nothing'  is a  dummy variable  declared in  MULTI for  Forks to tasks
  482.     which have  no real parameters. Normally  all the tasks are  treated as
  483.     the first  task, they are simply  terminated if the program  HALT()s or
  484.     RunError()s. But  if a task  (who else  can  know if the  task wants to
  485.     deinitialize ?) wants to deinitialize, it  has to tell MULTI by setting
  486.     t^.hasexit to  TRUE. 't' is a  global pointer in MULTI  which points to
  487.     the current task,  that means if the main  task says t^.hasexit:=false,
  488.     it is terminated if a task produces a run-time error. The main task has
  489.     an deinitialization by default.
  490.  
  491.  
  492.  
  493.  
  494.  
  495.  
  496.     Chapter 5: Advanced Multitasking - Semaphores
  497.  
  498.     Hey cool, semaphore. Another word you  have never heard of. (If English
  499.     is your native language, you know what that is, don't be insulted. This
  500.     document covers Germans, too, and most  Germans don't know what a sema-
  501.     phore is)
  502.  
  503.     It does not matter if you have heard of that before, I will explain the
  504.     concept of them  now. Here's what my lexicon says  : "system of sending
  505.     signals  by holding  the arms  or  two  flags in  certain positions  to
  506.     indicate letters of the alphabet" and "device with red and green lights
  507.     on mechanically moved arms, used for signalling on railways."
  508.  
  509.     Multi's  semaphores are  more like  the latter  definition. While it is
  510.     essential for two tasks to be insulated from each other, they need some
  511.     way of  communicating with each other.  As example I present  two tasks
  512.     counting from 1  to 1000, but the second wants  to wait until the first
  513.     one has counted to 500. The first approach would be a global variable :
  514.  
  515.     │  uses multi;
  516.     │
  517.     │  var
  518.     │    task_2_should_start_now : boolean;
  519.     │
  520.     │  procedure task_1(var m); far;
  521.     │  var w : word;
  522.     │  begin
  523.     │    task_2_should_start_now := false;
  524.     │    w := 1;
  525.     │    repeat
  526.     │      inc(w);
  527.     │      if w = 500 then task_2_should_start_now := true;
  528.     │      Switch
  529.     │    until w = 1000;
  530.     │  end;
  531.     │
  532.     │  procedure task_2(var m); far;
  533.     │  var w : word;
  534.     │  begin
  535.     │    repeat Switch until task_2_should_start_now;
  536.     │    w := 1;
  537.     │    repeat
  538.     │      inc(w);
  539.     │      Switch
  540.     │    until w = 1000;
  541.     │  end;
  542.     │
  543.     │  begin
  544.     │    Fork(task_1,2048,Nothing);
  545.     │    Fork(task_2,2048,Nothing);
  546.     │    Terminate
  547.     │  end.
  548.  
  549.  
  550.  
  551.     This should do it, but it violates  our Prime Directive (yeah, I am yet
  552.     another  Trekkie) of  not interfering  with other  tasks. Why  yield to
  553.     crusty principles, you  might ask. Well, that is  not just a principle,
  554.     it is the  only (yes, THE *ONLY*) way to  make your program's behaviour
  555.     unterstandable for  mere humans. If you  have several tasks interfering
  556.     with each other by means of some global variables, you will soon die of
  557.     an heart attack.  By the way, you have read  right, you can still debug
  558.     your  multitasking  programs  !  From  within  the  IDE  and with Turbo
  559.     Debugger, everything works  fine ! You can even F8  over SWITCH and all
  560.     the other tasks will then execute and you will be in your task again !
  561.  
  562.     So how  should two tasks  communicate without global  variables ? Yeah,
  563.     right,  that  won't  work.  I  present  semaphores  as  solution to the
  564.     problem, but  semaphores are variables,  too. You can't  use the Vulcan
  565.     Mind Touch,  so for now  think of semaphores  as syntactical sugar  for
  566.     booleans. The syntax is this :
  567.  
  568.     │  uses multi;
  569.     │
  570.     │  var
  571.     │    task_1_is_ready : semaphore;
  572.     │
  573.     │  procedure task_1(var m); far;
  574.     │  var w : word;
  575.     │  begin
  576.     │    InitSemaphore(task_1_is_ready);
  577.     │    w := 1;
  578.     │    repeat
  579.     │      inc(w);
  580.     │      if w = 500 then
  581.     │        Release(task_1_is_ready);
  582.     │      Switch
  583.     │    until w = 1000;
  584.     │  end;
  585.     │
  586.     │  procedure task_2(var m); far;
  587.     │  var w : word;
  588.     │  begin
  589.     │    WaitFor(task_1_is_ready);
  590.     │    w := 1;
  591.     │    repeat
  592.     │      inc(w);
  593.     │      Switch
  594.     │    until w = 1000;
  595.     │  end;
  596.     │
  597.     │  begin
  598.     │    Fork(task_1,2048,Nothing);
  599.     │    Fork(task_2,2048,Nothing);
  600.     │    Terminate
  601.     │  end.
  602.  
  603.  
  604.  
  605.  
  606.     This does  not only look  different, it is  more efficient, too,  since
  607.     MULTI does not switch to tasks  waiting for a semaphore. It complicated
  608.     MULTI's code  considerably, but it is  much nicer. Tasks waiting  for a
  609.     semaphore with t^.hasexit=TRUE are still not terminated, but WAITFOR is
  610.     a function, too. Instead of asking if SWITCH is true you can now ask if
  611.     WAITFOR is true. If WAITFOR is true, you should deinit immediately.
  612.  
  613.     Of course MULTI can handle several tasks waiting for a semaphore.
  614.  
  615.     There  are situations  where you  want to  kill tasks  which wait for a
  616.     semaphore, because  you know the  semaphore will never  be released. In
  617.     that case,  MULTI offers the  procedure KAMIKAZE, which  terminates (or
  618.     lets deinitialize) all waiting tasks.
  619.  
  620.     Mhh, you  see and understand that  code, but you can't  understand what
  621.     those semaphores  are good for ?  They are  needed for the  next way of
  622.     interprocess communication, pipes.
  623.  
  624.  
  625.  
  626.  
  627.  
  628.  
  629.  
  630.  
  631.  
  632.  
  633.  
  634.  
  635.  
  636.  
  637.  
  638.  
  639.  
  640.  
  641.  
  642.  
  643.  
  644.  
  645.  
  646.  
  647.  
  648.  
  649.  
  650.  
  651.  
  652.  
  653.  
  654.  
  655.  
  656.  
  657.  
  658.  
  659.  
  660.  
  661.     Chapter 6: Interprocess Communications Via Pipes
  662.  
  663.     What do I mean with 'pipe'...? 'pipe' is a metaphora for something long
  664.     and dark where you put in something on one side and it comes out on the
  665.     other.  The important  point about  it is  that one  side does not know
  666.     about the other  side. That sounds superfluous, doesn't it  ? But it is
  667.     not. Imagine you  write a program which reads from  a pipe and displays
  668.     the result in a Turbo Vision  window. Or a Windoze window. Or wherever.
  669.     Then you  only have to implement  new pipes to expand  the program. You
  670.     could write  tasks which read from  a pipe and write  to the modem. You
  671.     could write error correcting pipes  and just have tasks communicate via
  672.     pipes, and they  don't even have to run on  the same computer, they can
  673.     communicate via modem, network etc. and the tasks wouldn't know it. Any
  674.     with semaphores the pipes become efficient, since we can fully make use
  675.     of MULTI's possibilities. If a task reads  from a pipe and there are no
  676.     data in  the pipe, that task  is put "asleep" waiting  for a semaphore.
  677.     You can  have more than  one task read  from a pipe,  but that does not
  678.     make sense.  It may make  sense to have  more than one  task write to a
  679.     pipe, MPIPES is able to do that, anyway.
  680.  
  681.     So the  next goal is to  write tasks which do  device dependent in- and
  682.     output to and  from pipes. Have a look into  the units MPKBD, MPTTYCRT,
  683.     MPLOOP and MPFOSSIL  for such tasks (I call  them "clients", since they
  684.     don't do much, they only provide an interface).
  685.  
  686.     When initializing a pipe you have to say how much bytes buffer you want
  687.     to have. Then you can put a byte into it with Put, get a byte out of it
  688.     with Get, put or get several bytes  with PutBin and GetBin, or close it
  689.     with Done. Some example code :
  690.  
  691.     │  uses multi, mpipes;
  692.     │
  693.     │  var
  694.     │    t : tPipe;
  695.     │    s : string;
  696.     │
  697.     │  procedure GetTask(var m); far;
  698.     │  var s : string;
  699.     │      t : tpipe absolute m;
  700.     │  begin
  701.     │    s[0] := t.Get;                       { Read length byte }
  702.     │    t.GetBin(s[1],length(s));     { Then the rest of string }
  703.     │    writeln('GetTask received: ',s);
  704.     │    Terminate;
  705.     │  end;
  706.     │
  707.     │  begin
  708.     │    s := 'Hallo, Welt';
  709.     │    t.Init(10);           { Open pipe with 10 bytes buffers }
  710.     │    Fork(GetTask,4096,t,'GetTask');
  711.     │           { Create task which reads a string from the pipe }
  712.     │    t.PutBin(s,length(s)+1);   { Write a string to the pipe }
  713.     │    Terminate;
  714.     │      { Terminate this task; let GetTask display the string }
  715.     │  end.
  716.  
  717.     Two things are  noteworthy about this code : First  the pipe is smaller
  718.     than the data put into it by  PutBin. That means, the pipe handles that
  719.     for you, it puts your data into the pipe and returns after that. In the
  720.     meantime it  may have been  put asleep several  times and you  wouldn't
  721.     notice it !
  722.  
  723.     The second thing  is that Fork gets a fourth  parameter here, a string.
  724.     This is  a debug mode  I have implemented  in MULTI. Programming  it is
  725.     much harder  that programming *for* it.  That's why I wrote  that debug
  726.     mode. Just {$DEFINE DEBUG} when compiling MULTI and the other units and
  727.     then several things change. Fork gets a string as fourth parameter, you
  728.     get debug dumps on your monochrome  monitor (if you have one, otherwise
  729.     your  system will  crash ;)  or  you  adjust DUAL.PAS),  and all  major
  730.     actions  about tasks  are reported  on the  monochrome monitor.  If you
  731.     want the  dumps (a list of  all active tasks) just  debugdump:=true, if
  732.     you want the actions logged  on the monochrome screen just debug:=true.
  733.     This can be a great tool  for problem diagnosis ("Why doesn't that task
  734.     read the pipe ?").
  735.  
  736.     For your  (and mine) convenience I  have written PutS and  GetS methods
  737.     which write and read strings to  pipes (first the length byte, then the
  738.     data). So  if both tasks know  what they are doing,  you can use these,
  739.     too.
  740.  
  741.     Also, I  have implemented ReadLn  and WriteLn methods  for strings from
  742.     and to pipes, they append the usual DOS #13#10 pair to the string, then
  743.     put it. This simplifies writing of  a Terminal through pipes. I plan to
  744.     write a CRT replacement through  pipes with a Terminal emulation. Don't
  745.     know how soon I even start that.
  746.  
  747.     To test that ReadLn  and WriteLn I wrote a new pipe  client to and from
  748.     files (Unit MPFile).  When doing that I discovered  another thing I had
  749.     forgot about the pipes. What if you set up a task which does input from
  750.     a pipe and  a task which does  output, and the output  task fails ? The
  751.     input  task is  put asleep  and will  never awaken.  The pipe will stay
  752.     around and waste memory. So the next step in pipe development is what I
  753.     call 'broken  pipes'. I call it  that way because Linux  writes that in
  754.     situations  like those  I believe.  But how  should the  pipe know that
  755.     something  is wrong  ? Each  client now  has to  register with the pipe
  756.     (with the  new pipe methods  NewInputTask and NewOutputTask)  and check
  757.     out (NoMoreInput and  NoMoreOutput). If the last task  of a kind checks
  758.     out and the  other kind waits it is terminated.  If the other kind does
  759.     not wait, it is killed when it  starts waiting. Then the pipe memory is
  760.     released.
  761.  
  762.  
  763.  
  764.  
  765.  
  766.  
  767.  
  768.  
  769.  
  770.  
  771.     Appendix A: Things I Have Forgotten So Far
  772.  
  773.  
  774.     All the  examples in this manual  and MULTI and the  accompanying units
  775.     assume you have "extended syntax" enabled by default {$X+}.
  776.  
  777.  
  778.     Fork is  a function, too, and  returns a pointer to  the new task. This
  779.     pointer can be used  to make that task wait for a  semaphore or to kill
  780.     that task.
  781.  
  782.  
  783.     A task is killed by setting the  boolean Poisoned to TRUE, that's where
  784.     the pointer  Fork returns comes in  handy. So a task  kills itself with
  785.     "t^.Poisoned :=  true". You can  kill other tasks  by saving what  Fork
  786.     returns and then setting fuckup :
  787.  
  788.       var t : semaphore;
  789.       { some code ... }
  790.       begin
  791.         t := Fork(mytask,2048,Nothing,'Dies young');
  792.         { do something ... }
  793.         t^.Poisoned := true;
  794.         { The next switch will kill or deinit that task }
  795.  
  796.     Kamikaze(t) works, and  kills all active tasks, so  that's probably not
  797.     what you wanted.
  798.  
  799.  
  800.     If your program dies and you don't know why, and it does not always die
  801.     in the same situation, some task probably has to little stack space. Or
  802.     your pointers have gone wild (that's  no problem of MULTI). Or MULTI is
  803.     broken, please contact me if you are sure that the latter is true !
  804.  
  805.  
  806.     As many other  sources I have written MULTI could  rot on my hard disk,
  807.     or it can go  all around the world and cause great  grief to people who
  808.     try to work with it. Maybe it is  helpful to somebody, any I hope it is
  809.     as helpful to you as it is to me ! This is just the very beginning, the
  810.     new possibilities are overwhelming. You can put almost anything through
  811.     a pipe, and you can route a pipe everywhere with a little work. Queries
  812.     to the database could work locally, through a network or via modem, and
  813.     neither the user nor the program would care. A terminal could operate a
  814.     remote modem somewhere in a network. You can simply simulate input to a
  815.     program via disk pipes. I could continue endlessly !
  816.